#include "database.h"
#include <iostream>
#include <utility>

////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
Database::Database()
	: pSystemUsers( 0 )
{
}

////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
Database::~Database()
{
	for( unsigned int i = 0; i < pSystemUsers.size(); ++i )
	{
		delete pSystemUsers[i];
		pSystemUsers[i] = 0;
	}

	pSystemUsers.clear();
}

////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
bool Database::UserExists( const std::string username )
{
	bool exists = false;

	for( unsigned int i = 0; i < pSystemUsers.size(); ++i )
	{
		if( pSystemUsers[i]->GetUsername() == username )
		{
			exists = true;
			break;
		}
	}

	return( exists );
}

////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
bool Database::IsValidUser( const std::string username, const std::string password )
{
	bool valid = false;

	for( unsigned int i = 0; i < pSystemUsers.size(); ++i )
	{
		if( pSystemUsers[i]->GetUsername() == username )
		{
			valid = true;
			break;
		}
	}

	return( valid );
}

////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
unsigned int Database::AuthenticateUser(const std::string username, const std::string password)
{
	unsigned int handle = 0;

	for( unsigned int i = 0; i < pSystemUsers.size(); ++i )
	{
		if( pSystemUsers[i]->GetUsername() == username )
		{
			if( (pSystemUsers[i]->GetPassword() == password) &&
					(pSystemUsers[i]->GetFailedLoginAttempts() < MAX_FAILED_LOGINS))
			{
				handle = pSystemUsers[i]->GetUserId () ;
				pSystemUsers[i]->ResetFailedLoginAttempts();
				pAuthenticatedUsers[handle] = pSystemUsers[i];
			}
			else
			{
				// Increment the invalid login attempts count for this user
				pSystemUsers[i]->IncrementFailedLoginAttemps();

			}
			break;
		}
	}

	return( handle );
}

////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
bool Database::AddUser( const std::string username, const std::string password )
{
	bool success = false;

	pSystemUsers.push_back( new User( username, password ) );

	success = true;

	return( success );
}

////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
void Database::EndUserSession( unsigned int const userHandle )
{
	AUTH_USERS_MAP::iterator it;

	for( it = pAuthenticatedUsers.begin(); it != pAuthenticatedUsers.end(); ++it )
	{
		if( it->first == userHandle)
		{
			it->second = NULL;
			pAuthenticatedUsers.erase( it );
			break;
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
bool Database::UserLockedOut( const std::string username )
{
	bool lockedOut = false;

	for( unsigned int i = 0; i < pSystemUsers.size(); ++i )
	{
		if( pSystemUsers[i]->GetUsername() == username )
		{
			if( pSystemUsers[i]->GetFailedLoginAttempts() >= MAX_FAILED_LOGINS )
			{
				lockedOut = true;
			}
			break;
		}
	}

	return( lockedOut );
}

////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
void Database::CreateCannedAccounts () 
{

   // initial amount in accounts is in pennies
   for ( uint32_t i = 0 ; i < 10 ; i++ )
   {
     if ( i%2 ) 
     {
       Account account ( i, Account::SAVINGS_ACCOUNT, i * 1234 ) ;   
       mAccounts.push_back ( account ) ;
     } else {
       Account account ( i, Account::CHECKING_ACCOUNT, i * 1234 ) ;   
       mAccounts.push_back ( account ) ;
     }
   }
}

////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
void Database::CreateCannedUsers () 
{
  // Create Users
  User * userA = new User ( "jbs000", "Test" ) ;
  User * userB = new User ( "jbs",    "Test" ) ;
  User * userC = new User ( "jbs0",   "Test" ) ;
  User * userD = new User ( "jbs00",  "Test" ) ;

  // Add Users
	pSystemUsers.push_back( userA );
	pSystemUsers.push_back( userB );
	pSystemUsers.push_back( userC );
	pSystemUsers.push_back( userD );

  // Map Accounts To Users
  // User A Accounts
  mUserAccountsMap.insert(std::pair<uint32_t, uint32_t>(userA -> GetUserId (), 0) );
  mUserAccountsMap.insert(std::pair<uint32_t, uint32_t>(userA -> GetUserId (), 5) );
   
  // User B Accounts
  mUserAccountsMap.insert(std::pair<uint32_t, uint32_t>(userB -> GetUserId (), 2) );
  mUserAccountsMap.insert(std::pair<uint32_t, uint32_t>(userB -> GetUserId (), 9) );
  mUserAccountsMap.insert(std::pair<uint32_t, uint32_t>(userB -> GetUserId (), 6) );

  // User C Accounts
  mUserAccountsMap.insert(std::pair<uint32_t, uint32_t>(userC -> GetUserId (), 4) );
  mUserAccountsMap.insert(std::pair<uint32_t, uint32_t>(userC -> GetUserId (), 3) );
  mUserAccountsMap.insert(std::pair<uint32_t, uint32_t>(userC -> GetUserId (), 1) );

  // User D Accounts
  mUserAccountsMap.insert(std::pair<uint32_t, uint32_t>(userD -> GetUserId (), 7) );
  mUserAccountsMap.insert(std::pair<uint32_t, uint32_t>(userD -> GetUserId (), 8) );
}

////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
void Database::DumpAccounts () 
{

  printf ( "\nPre Canned Accounts\n" ) ;
	for( unsigned int i = 0; i < mAccounts.size(); ++i )
	{
    printf ( "%2.2d : Account Id:%3.3d, Type: %10.10s, Balance = $%6.6s\n", 
          i+1,
          mAccounts[i].GetId (), 
          mAccounts[i].GetType () == Account::SAVINGS_ACCOUNT ? "Savings" : "Checking",
          mAccounts[i].GetBalance().GetValue () .c_str() ) ;
	}
  printf ( "\n" ) ;

  printf ( "\nPre Canned Users/Accounts\n" ) ;
  
  printf ("Account Count\n" ) ;
	for( unsigned int i = 0; i < pSystemUsers.size(); ++i )
  {
	  printf ( "Number of elements with key %d: %d\n", i, (int)mUserAccountsMap.count(i) ) ;
  }
  
  printf( "\n\nUsers & Accounts\n" ) ;
	for( unsigned int userIndex = 0; userIndex < pSystemUsers.size(); ++userIndex )
	{
    std::pair<std::multimap<uint32_t, uint32_t>::iterator, std::multimap<uint32_t, uint32_t>::iterator> accountIterator ;
	 
    accountIterator = mUserAccountsMap.equal_range( userIndex );
    // Loop through range of each user
    printf ( "\tUser %d (%s) Accounts:\n", userIndex, pSystemUsers[userIndex]->GetUsername ().c_str()  ) ;
    for ( std::multimap<uint32_t, uint32_t>::iterator it2 = accountIterator.first;
          it2 != accountIterator.second;
          ++it2)
    {
     printf ( "\t\tAccount Id:%3.3d, Type: %10.10s, Balance = $%6.6s\n", 
           mAccounts[(*it2).second].GetId (), 
           mAccounts[(*it2).second].GetType () == Account::SAVINGS_ACCOUNT ? "Savings" : "Checking",
           mAccounts[(*it2).second].GetBalance().GetValue () .c_str() ) ;
    }
    printf ( "\n" ) ;
 }

}

void Database::DisplayAccounts ( const uint32_t userId ) 
{
  std::pair<std::multimap<uint32_t, uint32_t>::iterator, std::multimap<uint32_t, uint32_t>::iterator> accountIterator ;
 
  accountIterator = mUserAccountsMap.equal_range( userId );

  // Loop through accounts of selected user
  printf ( "\tUser Accounts:\n" ) ;
  for ( std::multimap<uint32_t, uint32_t>::iterator it2 = accountIterator.first;
        it2 != accountIterator.second;
        ++it2)
  {
   printf ( "\t\tAccount Id:%3.3d, Type: %10.10s, Balance = $%6.6s\n", 
         mAccounts[(*it2).second].GetId (), 
         mAccounts[(*it2).second].GetType () == Account::SAVINGS_ACCOUNT ? "Savings" : "Checking",
         mAccounts[(*it2).second].GetBalance().GetValue () .c_str() ) ;
  }
  printf ( "\n" ) ;
}

bool Database::TransferFunds( const uint32_t userId,
													 	  const uint32_t fromAcctID,
												 	    const uint32_t toAcctID,
												 	    const double amount )
{
	bool success = true;

	std::pair<std::multimap<uint32_t, uint32_t>::iterator, std::multimap<uint32_t, uint32_t>::iterator> accountIterator ;
	accountIterator = mUserAccountsMap.equal_range( userId );

	std::multimap<uint32_t, uint32_t>::iterator fromAccount = accountIterator.second;
	std::multimap<uint32_t, uint32_t>::iterator toAccount = accountIterator.second;

  // Loop through accounts of selected user
  for ( std::multimap<uint32_t, uint32_t>::iterator it2 = accountIterator.first;
        it2 != accountIterator.second;
        ++it2)
  {
  	if( mAccounts[(*it2).second].GetId () == fromAcctID )
  	{
  		fromAccount = it2;
  	}

  	if( mAccounts[(*it2).second].GetId () == toAcctID )
  	{
  		toAccount = it2;
  	}
  }

  if( (fromAccount != accountIterator.second) &&
  		(toAccount != accountIterator.second) )
  {
  	if( mAccounts[(*fromAccount).second].WithDrawal( amount ) )
  	{
  		mAccounts[(*toAccount).second].Deposit( amount );
  	}
  	else
  	{
  		success = false;
  	}
  }
  else
  {
  	success = false;
  }

  return( success );
}




